/********************************************************************************************/
/* Fullhan Technology (Shanghai) Co., Ltd.                                                  */
/* Fullhan Proprietary and Confidential                                                     */
/* Copyright (c) 2017 Fullhan Technology Group Limited and its affiliated companies         */
/********************************************************************************************/
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include "drv_cesa_if.h"



FH_CESA_HANDLE_P g_cesa;

int32_t cesa_init(FH_CESA_HANDLE_P hdlr)
{
	FH_CESA_AUX_S *p_aux = kmalloc(sizeof(FH_CESA_AUX_S), GFP_KERNEL);

	if (p_aux)
	{
		p_aux->dev = hdlr->dev;
		hdlr->p_priv = p_aux;
		drv_cesa_aux_init(hdlr->p_priv);
		
		return CESA_SUCCESS;
	}
	else
	{
		return CESA_INIT_FAILED;
	}
}

int32_t cesa_deinit(FH_CESA_HANDLE_P hdlr)
{
	drv_cesa_aux_deinit(hdlr->p_priv);
	kfree(hdlr->p_priv);
	
	return CESA_SUCCESS;
}

static int cesa_open (struct inode *inode, struct file *file)
{
    return FH_SUCCESS;
}

static int cesa_release (struct inode *inode, struct file *file)
{
    return FH_SUCCESS;
}

static long cesa_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    void *parg = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
	int32_t ret = CESA_SUCCESS;
	CMD_ADES_CREATE_S *cmd_ades_create = (CMD_ADES_CREATE_S *)parg;
	CMD_ADES_DESTROY_S *cmd_ades_destroy = (CMD_ADES_DESTROY_S *)parg;
	CMD_ADES_CTRL_S *cmd_ades_ctrl = (CMD_ADES_CTRL_S *)parg;
	CMD_ADES_PROCESS_S *cmd_ades_process = (CMD_ADES_PROCESS_S *)parg;
	CMD_ADES_INFO_S *cmd_ades_info = (CMD_ADES_INFO_S *)parg;
	CMD_ADES_EFUSE_KEY_S *cmd_efuse_key = (CMD_ADES_EFUSE_KEY_S *)parg;
	CMD_ADES_ADDR_ATTR_S *cmd_ades_addr_attr = (CMD_ADES_ADDR_ATTR_S *)parg;


	switch (_IOC_DIR(cmd))
	{
    case _IOC_READ:
    case _IOC_WRITE:
    case (_IOC_WRITE | _IOC_READ):
        if (_IOC_DIR(cmd) & _IOC_WRITE)
            if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) {
                ret = -EFAULT;
				goto out;
            }
        break;
	default:
		ret = -EIO;
		goto out;
	}

	
	switch(cmd)
	{
	case CESA_IOC_ADES_CREATE:
		ret = drv_cesa_ades_create(&(cmd_ades_create->session), g_cesa->p_priv);
		cmd_ades_create->ret = ret;
		break;
	case CESA_IOC_ADES_DESTROY:
		ret = drv_cesa_ades_destroy(cmd_ades_destroy->session);
		cmd_ades_destroy->ret = ret;
		break;
	case CESA_IOC_ADES_CONFIG:
		ret = drv_cesa_ades_config(cmd_ades_ctrl->session, &(cmd_ades_ctrl->ctrl));
		cmd_ades_ctrl->ret = ret;
		break;
	case CESA_IOC_ADES_PROCESS:
		ret = drv_cesa_ades_process_ext(
					cmd_ades_process->session,
					cmd_ades_process->p_src_addr,
					cmd_ades_process->p_dst_addr,
					cmd_ades_process->length);
		cmd_ades_process->ret = ret;
		break;
	case CESA_IOC_ADES_ENCRYPT:
		ret = drv_cesa_ades_config_oper(
					cmd_ades_process->session,
					FH_CESA_ADES_OPER_MODE_ENCRYPT);
		if (ret == CESA_SUCCESS) {
			ret = drv_cesa_ades_process_ext(
					cmd_ades_process->session,
					cmd_ades_process->p_src_addr,
					cmd_ades_process->p_dst_addr,
					cmd_ades_process->length);
		}
		cmd_ades_process->ret = ret;
		break;
	case CESA_IOC_ADES_DECRYPT:
		ret = drv_cesa_ades_config_oper(
					cmd_ades_process->session,
					FH_CESA_ADES_OPER_MODE_DECRYPT);
		if (ret == CESA_SUCCESS) {
			ret = drv_cesa_ades_process_ext(
					cmd_ades_process->session,
					cmd_ades_process->p_src_addr,
					cmd_ades_process->p_dst_addr,
					cmd_ades_process->length);
		}
		cmd_ades_process->ret = ret;
		break;
	case CESA_IOC_ADES_GET_INFO:
		ret = drv_cesa_ades_get_infor(cmd_ades_info->session, &(cmd_ades_info->ctrl));
		cmd_ades_info->ret = ret;
		break;
	case CESA_IOC_ADES_EFUSE_KEY:
		ret = drv_cesa_ades_efuse_key(cmd_efuse_key->session, &(cmd_efuse_key->efuse_para));
		cmd_efuse_key->ret = ret;
		break;
	case CESA_IOC_ADES_ADDR_ATTR:
		ret = drv_cesa_ades_addr_attr(
					cmd_ades_addr_attr->session,
					cmd_ades_addr_attr->src_addr_attr,
					cmd_ades_addr_attr->dst_addr_attr);
		cmd_ades_addr_attr->ret = ret;
		break;

	default:
		ret = -EINVAL;
		goto out;
	}
	
    /*  copy results into user buffer  */
	switch (_IOC_DIR(cmd)) {
		case _IOC_READ:
		case (_IOC_WRITE | _IOC_READ):
			if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
				ret = -EFAULT;
			break;
	}

out:
	kfree(parg);
	return ret;
}

static const struct file_operations cesa_fops = {
	.owner		= THIS_MODULE,
	.open		= cesa_open,
	.unlocked_ioctl	= cesa_ioctl,
	.release	= cesa_release
};

static struct miscdevice cesa_miscdev = {
	.fops		= &cesa_fops,
	.name		= "cesa",
	.minor		= CIPHER_MINOR,
};



static irqreturn_t cesa_ades_int(int irq, void *priv)
{
	drv_cesa_ades_intr(g_cesa->p_priv);

	return IRQ_HANDLED;
}

static int cesa_drv_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *np = pdev->dev.of_node;
	struct resource *res;
	int ret;

	g_cesa =
		(FH_CESA_HANDLE_P)kmalloc(sizeof(FH_CESA_HANDLE_S), GFP_KERNEL);

	if (np && !IS_ERR(np)) {
		g_cesa->ades_reg = (uint32_t)of_iomap(np, 0);
		if (IS_ERR((void __iomem *)g_cesa->ades_reg))
			return PTR_ERR((void __iomem *)g_cesa->ades_reg);

		g_cesa->ades_irq = irq_of_parse_and_map(np, 0);
		if (g_cesa->ades_irq < 0)
			return g_cesa->ades_irq;
	} else {
		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
		g_cesa->ades_reg = (uint32_t)devm_ioremap_resource(dev, res);
		if (IS_ERR((void __iomem *)g_cesa->ades_reg))
			return PTR_ERR((void __iomem *)g_cesa->ades_reg);

		g_cesa->ades_irq = platform_get_irq(pdev, 0);
		if (g_cesa->ades_irq < 0)
			return g_cesa->ades_irq;
	}

	g_cesa->dev = dev;

	ret = cesa_init(g_cesa);
	if (ret)
		return ret;
	
	ret = devm_request_threaded_irq(dev,
					g_cesa->ades_irq,
					NULL,
					cesa_ades_int,
					IRQF_ONESHOT,
					dev_name(&pdev->dev),
					NULL);
	if (ret)
		return ret;

#ifdef CONFIG_CRYPTODEV
	fh_ce_register_algs(dev);
#endif

	return misc_register(&cesa_miscdev);
}

static int cesa_drv_remove(struct platform_device *pdev)
{	
	cesa_deinit(g_cesa);
	free_irq(g_cesa->ades_irq, g_cesa);
	kfree(g_cesa);

#ifdef CONFIG_CRYPTODEV
	fh_ce_unregister_algs(&pdev->dev);
#endif

	misc_deregister(&cesa_miscdev);

	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id cesa_of_match[] = {
	{.compatible = "fh,fh-cesa",},
	{},
};

MODULE_DEVICE_TABLE(of, cesa_of_match);
#endif

static struct platform_driver cesa_driver = {
	.probe		= cesa_drv_probe,
	.remove		= cesa_drv_remove,
	.driver		= {
		.name	= "fh_cesa",
#ifdef CONFIG_OF
		.of_match_table = cesa_of_match,
#endif
		.owner	= THIS_MODULE,
	},
};
module_platform_driver(cesa_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Circle Xue <xuejy344@fullhan>");
MODULE_DESCRIPTION("Fullhan's cryptographic engine driver");


